package net.pimpas.scanner;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.regex.Pattern;

import net.pimpas.core.scanner.Scanner;
import net.pimpas.core.scanner.exception.ScannerException;
import net.pimpas.core.visitor.ClassPathVisitor;
import net.pimpas.framework.PimpasFramework;
import net.pimpas.util.reflection.ReflectionUtil;

public class ClassPathScanner implements Scanner<Object> {
	
	private static final String RIGHT_SLASH		= "/";
	private static final String DOT				= ".";
	private static final String PATH_SEPARATOR	= System.getProperty("path.separator");
	private static final String FILE_SEPARATOR	= System.getProperty("file.separator");
	private static final String SPACE			= "%20";
	private static final String SIMPLE_SPACE	= " ";
	private static final String CLASS_EXTENSION	= ".class";
	private static final String JAR_EXTENSION   = ".jar";
	private static final List<String> IGNORE_JARS = new ArrayList<String>();
	//private static final Logger logger = Logger.getLogger(ClassPathScanner.class);
	
	static {
		/*
		IGNORE_JARS.add(new IgnoreFile("commons-", IgnoreFile.BEGIN_POSITION));
		IGNORE_JARS.add(new IgnoreFile("tools.jar", IgnoreFile.EQUALS));
		IGNORE_JARS.add(new IgnoreFile("bootstrap.jar", IgnoreFile.EQUALS));
		*/
	}
	
	public static void addIgnoreJar(String regex) {
		for(int i=0;i<IGNORE_JARS.size();i++)
			if(IGNORE_JARS.get(i).equals(regex)) return;
		
		//logger.debug("[CLASSPATHSCANNER]: Adding REGEX ["+regex+"] to skip jars in scanner!");
		
		IGNORE_JARS.add(regex);
	}
	
	public String getName() {
		return this.getClass().getName();
	}

	public List<Object> getResult() {
		return null;
	}

	public void scan() throws ScannerException {
		String classPath = System.getProperty("java.class.path");
		URL base = getClass().getClassLoader().getResource("/../lib/");
		if(base != null) classPath += PATH_SEPARATOR+base.getPath().replaceAll(SPACE, SIMPLE_SPACE);
		base = getClass().getClassLoader().getResource("/../classes/");
		if(base != null) classPath += PATH_SEPARATOR+base.getPath().replaceAll(SPACE, SIMPLE_SPACE);
		File f = null;
		String[] paths = classPath.split(PATH_SEPARATOR);
		String emptyString = "";
		for(int i=0;i<paths.length;i++,f=null) {
			// jar file, directory or simple java class
			f = new File(paths[i]);
			if(f.exists() && f.isDirectory()) {
				// directory
				try {
					this.runDirectory( emptyString, f );
				} catch (Exception e) {
					throw new ScannerException(e);
				}
			} else {
				// check jar file
				if(f.getName().endsWith(JAR_EXTENSION)) {
					// yes, jar file
					try {
						this.runJarFile( f );
					} catch (URISyntaxException e) {
						throw new ScannerException(e);
					}
				} else
					if(this.isClassFile(f.getName()))
						this.runClass(f.getPath(), f.getName());
			}
		}
		
		paths = null;
		System.gc();
	}
	
	private void alertClassVisitor(Class<?> clazz) {
		for(int i=0;i<PimpasFramework.getCurrentInstance().getVisitors().size();i++)
			if(ReflectionUtil.isSubclass(PimpasFramework.getCurrentInstance().getVisitors().get(i).getClass(), ClassPathVisitor.class))
				((ClassPathVisitor)PimpasFramework.getCurrentInstance().getVisitors().get(i)).visitClass(clazz);
	}

	
	private void alertFileVisitor(URL file) {
		for(int i=0;i<PimpasFramework.getCurrentInstance().getVisitors().size();i++)
			if(ReflectionUtil.isSubclass(PimpasFramework.getCurrentInstance().getVisitors().get(i).getClass(), ClassPathVisitor.class))
				((ClassPathVisitor)PimpasFramework.getCurrentInstance().getVisitors().get(i)).visitFile(file);
	}
	
	private void alertDirectoryVisitor(URL file) {
		for(int i=0;i<PimpasFramework.getCurrentInstance().getVisitors().size();i++)
			if(ReflectionUtil.isSubclass(PimpasFramework.getCurrentInstance().getVisitors().get(i).getClass(), ClassPathVisitor.class))
				((ClassPathVisitor)PimpasFramework.getCurrentInstance().getVisitors().get(i)).visitDirectory(file);
	}
	
	private void alertPackageVisitor(Package pkg) {
		for(int i=0;i<PimpasFramework.getCurrentInstance().getVisitors().size();i++)
			if(ReflectionUtil.isSubclass(PimpasFramework.getCurrentInstance().getVisitors().get(i).getClass(), ClassPathVisitor.class))
				((ClassPathVisitor)PimpasFramework.getCurrentInstance().getVisitors().get(i)).visitPackage(pkg);
	}
	
	private void runClass(String path, String fileName) {
		if(path != null)
			if(fileName.indexOf(path) >= 0)
				fileName = fileName.substring(fileName.indexOf(path)+path.length()+1);

		String clazx = null;
		clazx = fileName.replaceAll("\\"+FILE_SEPARATOR, DOT).replaceAll(RIGHT_SLASH, DOT);
		clazx = clazx.substring(0,clazx.indexOf(CLASS_EXTENSION));
		Class<?> clazz = null;
		try {
			clazz = Class.forName(clazx, false, getClass().getClassLoader());
			// call package visitors
			this.runPackages(clazz.getPackage());
			// call class visitors
			this.alertClassVisitor(clazz);
		} catch(Throwable e) {
		}
	}
	
	private void runPackages(Package pkg) {
		this.alertPackageVisitor(pkg);
	}

	
	private String isJarDenied( File f ) {
		Pattern pattern = null;
		for(String ignore: IGNORE_JARS) {
			pattern = Pattern.compile(ignore, Pattern.CASE_INSENSITIVE);
			if(pattern.matcher(f.getName()).find())
				return ignore;
		}
		
		return null;
	}

	private void runJarFile(File f) throws URISyntaxException {
		// check denied jars
		//String regex = null;
		if(this.isJarDenied(f)!=null) {
			//logger.debug("[ClassPathScanner]: Skipping Denied Jar ["+f.getName()+"] with regex ["+regex+"]");
			return;
		}
		JarFile jar;
		try {
			jar = new JarFile(f);
			Enumeration<JarEntry> entrys = jar.entries();
			JarEntry entry = null;
			while(entrys.hasMoreElements()) {
				entry = entrys.nextElement();
				if(entry.isDirectory()) {
					this.alertDirectoryVisitor(this.getClass().getClassLoader().getResource(entry.getName()));
				} else
					// call file visitors
					this.alertFileVisitor(this.getClass().getClassLoader().getResource(entry.getName()));

				if(isClassFile(entry.getName()))
					this.runClass(null, entry.getName());
			}
		} catch (IOException e) {
			
		}
	}

	private boolean isClassFile(String fileName) {
		return fileName.endsWith(CLASS_EXTENSION);
	}

	private void runDirectory(String path, File f) throws MalformedURLException, URISyntaxException {
		if(f == null) return;
		if(f.isDirectory())
			this.alertDirectoryVisitor(f.toURI().toURL());
		else {
			// call visitors file
			this.getClass().getClassLoader().getResource(f.getAbsolutePath());
			this.alertFileVisitor(f.toURI().toURL());
		}

		File[] files = f.listFiles();
		if(files != null) {
			for(int i=0;i<files.length;i++)
				runDirectory(path+files[i].getName()+FILE_SEPARATOR, files[i]);
			files = null;
		}

		if(this.isClassFile(f.getName()))
			this.runClass(f.getPath(), path);
		if(f.getName().endsWith(JAR_EXTENSION))
			this.runJarFile(f);
	}
	
}